本篇將介紹到
我們開始吧!
GoF 說道:
(Decorator 模式)動態地給一個物件一些額外的職責。就增加功能來說,decorator 模式比生成子類別更為靈活。
它的工作原理:
這次我們先來看看 decorator 策略的 UML 圖

本圖隱含了上述的物件鏈:
Component 物件(ConcreteComponent 或 Decorator)Decorator 物件後面都接著另一個 Decorator 或是 ConcreteComponent
ConcreteComponent 物件Interesting, right?
我相信到這裡可能還是有點模糊,不知該怎麼實踐它。我們接下來就看看案例。
本例是 Day10 提到的電子商務系統,就不在此贅述。
我們當時有說到,當 SalesOrder 計算完商品金額後,它會使用 SalesTicket 幫忙列印票券,如下圖。

今天有了新需求:我們需要為 SalesTicket 物件新增表頭 (header) 與頁腳 (footer)。
簡單的實踐辦法會是:新增 Header、Footer 物件,讓 SalesTicket 使用該兩個物件, 配上 switch-case 做判斷,來將 header 與 footer 加上去。
但是如果今天的判斷變複雜了:如果今天有多種 headers 與 footers 呢?如果有機會會需要同時有兩種 headers 在同一張票券呢?
多種組合的可能,可能使上述簡單的結構無法承擔。
這時候如果我們引入 decorator 模式,我們就會有下面的圖:

此處的 Client 即是 SalesOrder。我們接下來來看實踐的程式碼如何。
Client 做的事:
class Client {
public static void main(String[] args) {
Factory myFactory;
myFactory = new Factory();
Component myComponent = myFactory.getComponent();
}
}
Component 抽象類別與其一的衍生類別:SalesTicket(上述的 ConcreteComponent):
abstract class Component {
abstract public void printTicket();
}
class SalesTicket extends Component {
public void printTicket() {
// 列印銷售票券的程式碼
}
}
各種 Decorator:
abstract class TicketDecorator extends Component {
private Component myTrailer;
public TicketDecorator(Component myComponent) {
myTrailer = myComponent;
}
public void callTrailer() {
if (myTrailer != null) myTrailer.printTicker();
}
// 各種具體 Decorator 類別
public class Header1 extends TicketDecorator {
public Header1(Component myComponent) {
super(myComponent);
}
public void printTicket() {
// 列印 Header1 的程式碼
super.callTrailer();
}
}
public class Header2 extends TicketDecorator {
public Header2(Component myComponent) {
super(myComponent);
}
public void printTicket() {
// 列印 Header2 的程式碼
super.callTrailer();
}
}
public class Footer1 extends TicketDecorator {
public Footer1(Component myComponent) {
super(myComponent);
}
public void printTicket() {
super.callTrailer();
// 列印 Footer1 的程式碼
}
}
public class Footer2 extends TicketDecorator {
public Footer2(Component myComponent) {
super(myComponent);
}
public void printTicket() {
super.callTrailer();
// 列印 Footer2 的程式碼
}
}
}
產生物件鏈的工廠物件
class Factory {
public Component getComponent() {
Component myComponent;
myComponent = new SalesTicket();
myComponent = new Footer1(myComponent);
}
}
觀察最後這個 Factory,我們可以知道 myFacory.getComponent() 會使這張 ticket 印出來的樣式為:
HEADER1
SALES TICKET
FOOTER1
如果今天我們需要的是
HEADER1
HEADER2
SALES TICKET
FOOTER1
那麼 myFactory.getComponent() 所需要的物件鏈就會是
return new Header1(new Header2(new Footer1(new SalesTicket())));
以此類推。
以下是 decorator 模式的關鍵特徵:
| 項目 | 內容 |
|---|---|
| 意圖 | 動態地給一個物件增加職責 |
| 問題 | 要使用的物件將執行所需的基礎功能。但可能需要為這個物件增加某些功能,附加功能可能在基礎功能之前或之後。 |
| 解決方案 | 可以無須建立子類別,而擴展一個物件的功能 |
| 參與者與協作者 | ConcreteComponent 讓 Decorator 物件為自己增加功能。有時候也可能用 ConcreteComponent 的衍生類別提供核心功能。Component 類別定義了所有上述類別所使用的介面。 |
| 效果 | 增加的功能放進小物件中。這樣可以動態地在 ConcreteComponent 物件之前或之後新增功能。注意到物件鏈必終於 ConcreteComponet。 |
| 實作 | 建立一個抽象類別來表示原類別和要增加到這個類別的新功能。在裝飾類別中,將對新功能的呼叫放在緊隨其後的物件的呼叫之前或之後,以獲得正確的順序。 |
我們要注意到這個模式有幾個約束因素:
Decorator 可遵循也可不遵循所有規則Decorator 物件們,但又不能增加客戶負擔(使客戶增加某些職責)Decorator 物件的職責。試著 follow 上述的約束,可以讓 decorator 模式的意圖與實作分離開來。
接下來,我們將會介紹 Observer 模式。明天見囉!